/**
 * 
 */
package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions;

import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.GUIDStorage;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.Character;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.AbilityValue;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.Stats;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.BattlePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.SimplePlayer;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Chessboard;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.AttackEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.EventManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.GenericEventManager;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.InvalidPointSettingEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.NoSkillPointEvent;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.events.SelectedCharacterEvent;

import java.math.BigDecimal;
import java.util.Iterator;
import java.util.LinkedList;

import javax.print.attribute.standard.Finishings;

/**
 * @author Massimo Canonaco
 *
 */
public class CharacterAttackAction extends AbstractCharacterAction {

	/** */ // TODO: SET CHARACTERATTACKACTION COST FROM GAME MANAGER
	static public final int COST = 1;
	
	/** */
	protected BattlePlayer player;
	
	/** */
	protected BattlePlayer enemyPlayer;

//	/** */ //TODO: set CharacterAttackAction GUID
	static public final float GUID = GUIDStorage.CHARACTER_ATTACK_ACTION;
	
	/** */
	static public final String NAME = "simplyattack";
	
	/** */
	public CharacterAttackAction(SimplePlayer performer, Chessboard arena, BattlePlayer player, BattlePlayer enemyPlayer) {
		super(performer, arena);
		this.player = player;
		this.enemyPlayer = enemyPlayer;
	}

	/* (non-Javadoc)
	 * @see it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.Action#getActionIdentifier()
	 */
	@Override
	public float getActionIdentifier() 
	{
		return GUIDStorage.CHARACTER_ATTACK_ACTION;
	}

	/* (non-Javadoc)
	 * @see it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.Action#execute(java.util.LinkedList)
	 */
	@Override
	public boolean execute(LinkedList<GenericActionInfo> infos) {
		// Set variables before execution.
		setting(infos);
	
		// If all is set we can start the execution of the action.
		if(check())
		{
			// Get the target character on the target point.
			Character targetCharacter = this.getCharacter(targetPoint.row, targetPoint.column);
			
			// If there's no character on the target point.
			if(targetCharacter == null)
			{
				//Throw invalid target event
				EventManager.getInstance().triggerEvent(new InvalidPointSettingEvent(false, targetPoint.row, targetPoint.column));
				
				// Reset the targetPoint
				targetPoint.reset();
				
				System.err.println("NO TARGET");
				// Return false, we cannot execute the action.
				return false;
			}
			
			// Get the statics of the target character.
			Stats targetStats = targetCharacter.getStats().getCharacteristics();

			// it was too cost effective for 1.0 ratio for 1 skill point
			float damage = character.getStats().getCharacteristics().getPhysicalForce()/2;
			
			float reduction; 
		
				reduction = calculateReductionDamage(targetStats.getPhysicalResistance());
			
			
			damage -= damage/100*reduction;
			
			targetStats.setHealth(targetStats.getHealth()-damage);
		
			// Check if the target character is still alive after the attack.
			// If he's not drop him from the board and update the enemy player.
			boolean alive = targetCharacter.isAlive();
			
			// If the target character is dead
			if(!alive)
			{
				// Remove him from the board.
				park.dropPawn(targetPoint.row, targetPoint.column);
				
				// Signal to the enemy player that he lost one character.
				enemyPlayer.update(0);
			}
			
			// Update player's skill points.
			player.subtractSkillPoints(COST);
			// Update spell casting to false
			character.setSpellCastable(false);
			// Throw attack event.
			EventManager.getInstance().triggerEvent(new AttackEvent(alive, false, NAME, character, targetCharacter, damage));
			System.out.println("ATTACK POPPATO "+character.getRow()+" "+character.getColumn()+ " ... "+targetCharacter.getRow()+" "+targetCharacter.getColumn());
			end = true;
			return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.Action#reset()
	 */
	@Override
	public void reset() 
	{
		character = null;
		if(targetPoint != null)
			targetPoint.reset();
	}
	
	/**Sets variables looking through the <i>infos</i> list.
	 * @param infos			contains the informations for setting the variables.*/
	protected void setting(LinkedList<GenericActionInfo> infos)
	{
		boolean cicle = true;
		GenericActionInfo genericInformation = null;
		Iterator<GenericActionInfo> iterator = infos.iterator();
		// We scan all the informations into the list.
		while(iterator.hasNext() && cicle)
		{
			// Get the information from the list.
			genericInformation = iterator.next();
			
			// If it's for setting the 'coordinate' to be used for taking the source and target.
			if(genericInformation.getGUID() == ActionCoordinateInfo.GUID)
			{
				// Remove it from the list because we're going to use it.
				iterator.remove();
				
				// If the character who will attack is not set, so he's null.
				if(character == null)
				{
					// Take the coordinates.
					ChessboardPointCoordinate point = ((ActionCoordinateInfo)genericInformation).getPoint();
					
					// Take the character by the points using the board.
					character = this.getCharacter(point.row, point.column);
					
					// If there's no character or he's not owned by the player who wants to do the action.
					if(character == null || character.getOwner() != performer)
					{
						if(character == null)
							System.out.println("personaggio non trovato");
						else
						{
							System.out.println("non tuo");
							System.out.println(character.getOwner().getNickname()+" != "+performer.getNickname());
						}
						// Set the character to null.
						character = null;
						
						// Signal an invalid source is passed.
						EventManager.getInstance().triggerEvent(new InvalidPointSettingEvent(true, point.row, point.column));
						
						// Stop setting.
						cicle = false;
						
						System.out.println("invalid SOURCE");
//						// Release the informations. //TODO: optimize this part of code for only one 'for'.
//						for (GenericActionInfo genericActionInfo : infos) 
//						{
//							genericActionInfo.setBusy(false);
//						}
						// Clear the info list.
						infos.clear();
					}
					else
					{
						EventManager.getInstance().triggerEvent(new SelectedCharacterEvent(character, character.getRow(), character.getColumn(), character.getStats().getCharacteristics().getAttackRange()));
					}
				}
				// If character is set, set the target point.
				else
				{
					// Get coordinate for target point.
					ChessboardPointCoordinate temp = ((ActionCoordinateInfo)genericInformation).getPoint();
					targetPoint.row = temp.row;
					targetPoint.column = temp.column;
					
					Character target = this.getCharacter(targetPoint.row, targetPoint.column);
					
					// If the targetPoint is out of range and contains a character who's owned by performer player. This info is another source information.
					if(target != null && target.getOwner() == performer)
					{
						// Reset the target point.
						targetPoint.reset();
						
						// Set the character to the new one.
						character = target;
						
						System.out.println("INVALID TARGET");
						EventManager.getInstance().triggerEvent(new SelectedCharacterEvent(character, character.getRow(), character.getColumn(), character.getStats().getCharacteristics().getAttackRange()));
					}
				}
				// Release the information.
				genericInformation.setBusy(false);
			}
		}
	}
	
	/**Checks if all variables are set up. If there's variables which aren't set up, the right event is thrown.
	 * @return True, variables are set up; false otherwise. */
	protected boolean check()
	{	
		// If player has no enough skill points for this action.
	
		if(player.getSkillPoints() < COST)
		{
			// Throw no skill point event.
			EventManager.getInstance().triggerEvent(new NoSkillPointEvent(NAME,player));
			System.out.println("NON ABBASTANZA POINT");
			return false;
		}
		// If character or target point is not set we can stop the execution of the action and return false for waiting these informations.
		else if(character == null || targetPoint == null || !targetPoint.isReady() )
		{
			if(character == null)
			System.out.println("c NULL HERE");
			
			if(targetPoint == null)
				System.out.println("t null");
			
			if(!targetPoint.isReady())
				System.out.println("t non ready");
			return false;
		}
		// If target point is not in range.
		else if(!character.isInAttackRange(targetPoint.row, targetPoint.column))
		{
			//Throw invalid target event
//			EventManager.getInstance().triggerEvent(new InvalidPointSettingEvent(false, targetPoint.row, targetPoint.column));
			
			// Reset the targetPoint
			targetPoint.reset();
			return false;
		}
		return true;
	}

	@Override
	public String getName() 
	{
		return NAME;
	}
	
	private static int REDUCTION_FACTOR = 500;
	protected float calculateReductionDamage(float stat) {
		//if character has a really "small" negative resistance
		// u can't reach breakeven point
		// so a maximum of 200% buff is returned
		if(stat < -(REDUCTION_FACTOR/1.5) )
			return -200;
		
		return round((1 - REDUCTION_FACTOR/(REDUCTION_FACTOR+stat))*100,2);
	}
	
	private float round(float d, int decimalPlace) {
	        BigDecimal bd = new BigDecimal(Float.toString(d));
	        bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
	        return bd.floatValue();
	    }
}
